package org.budo.ehcache.config.annotation.factory;

import java.io.File;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import org.budo.ehcache.config.annotation.EhCacheConfig;
import org.budo.ehcache.config.annotation.entity.EhCacheConfigEntity;
import org.budo.ehcache.replicator.EhcacheReplicatorFactory;
import org.budo.support.lang.util.IoUtil;
import org.budo.support.lang.util.ProcessUtil;
import org.budo.support.lang.util.ReflectUtil;
import org.budo.support.lang.util.StringUtil;
import org.budo.support.slf4j.Slf4j;
import org.budo.support.spring.io.util.ResourceUtil;
import org.slf4j.Logger;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;

import lombok.Getter;
import lombok.Setter;

/**
 * @author lmw
 * @see org.budo.ehcache.config.annotation.EhCacheConfig
 */
@Getter
@Setter
public class BudoEhcacheAnnotationConfigFactoryBean implements FactoryBean<Resource>, BeanFactoryPostProcessor {
    private static final Logger log = Slf4j.getLogger();

    /**
     * 默认值
     */
    private Class<?> cacheEventListenerFactory = EhcacheReplicatorFactory.class;

    private Boolean statistics;

    private Resource configLocation;

    private Map<String, EhCacheConfigEntity> cacheConfigMap = new ConcurrentHashMap<String, EhCacheConfigEntity>();

    private Set<BeanDefinition> beanDefinitions = new HashSet<BeanDefinition>();

    /**
     * 这是一个系数
     */
    private double maxElementsInMemory = 1;

    /**
     * 这是一个系数
     */
    private double maxElementsOnDisk = 1;

    public Map<String, EhCacheConfigEntity> getCacheConfigMap() {
        return Collections.unmodifiableMap(this.cacheConfigMap);
    }

    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        log.info("#69 postProcessBeanFactory, beanFactory=" + beanFactory);

        String[] beanNames = beanFactory.getBeanDefinitionNames();
        for (String beanName : beanNames) {
            BeanDefinition beanDefinition = beanFactory.getBeanDefinition(beanName);
            beanDefinitions.add(beanDefinition);
        }
    }

    public Resource getObject() throws Exception {
        return this.buildEhcacheXmlResource();
    }

    private Resource buildEhcacheXmlResource() {
        for (BeanDefinition beanDefinition : beanDefinitions) {
            String beanClassName = beanDefinition.getBeanClassName();
            if (StringUtil.isEmpty(beanClassName)) {
                continue;
            }

            List<Method> methods = this.getAllMethods(beanClassName);

            for (Method method : methods) {
                Cacheable cacheable = method.getAnnotation(Cacheable.class);
                if (null == cacheable) {
                    continue;
                }

                EhCacheConfig annotation = method.getAnnotation(EhCacheConfig.class);
                if (null == annotation) {
                    continue;
                }

                // 现在只支持一个
                String cacheName = null;

                // 低版本的 cacheable 无 cacheNames
                if (cacheable.value().length > 0) {
                    cacheName = cacheable.value()[0];
                } else if (cacheable.cacheNames().length > 0) {
                    cacheName = cacheable.cacheNames()[0];
                }

                EhCacheConfigEntity config = this.annotationToEhCacheConfigEntity(annotation, cacheName);

                this.cacheConfigMap.put(cacheName, config);
            }
        }

        Collection<EhCacheConfigEntity> configValues = this.getCacheConfigMap().values();
        List<String> configXmls = this.ehcacheConfigEntityToXml(configValues);

        String xml = this.configXmlHead() // 配置文件头
                + StringUtil.join(configXmls, "\n") // 正文
                + "\n</ehcache>"; // 配置文件尾

        log.info("#123, ehcache-xml=" + xml);

        return new ByteArrayResource(xml.getBytes());
    }

    private EhCacheConfigEntity annotationToEhCacheConfigEntity(EhCacheConfig annotation, String cacheName) {
        EhCacheConfigEntity config = new EhCacheConfigEntity();
        config.setCacheName(cacheName);

        // 总的地方配置一个系数，实际运行时两者向乘
        double maxElementsInMemory = this.getMaxElementsInMemory() * annotation.maxElementsInMemory();
        config.setMaxElementsInMemory((int) maxElementsInMemory);

        config.setTimeToIdleSeconds(annotation.timeToIdleSeconds());
        config.setTimeToLiveSeconds(annotation.timeToLiveSeconds());

        double maxElementsOnDisk = this.getMaxElementsOnDisk() * annotation.maxElementsOnDisk();
        config.setMaxElementsOnDisk((int) maxElementsOnDisk);

        // diskSpoolBufferSizeMB
        // diskExpiryThreadIntervalSeconds
        // memoryStoreEvictionPolicy
        // diskPersistent
        // eternal

        config.setStatistics(annotation.statistics());
        config.setGraph(annotation.graph());

        config.setCacheEventListenerFactory(annotation.cacheEventListenerFactory());

        return config;
    }

    private List<String> ehcacheConfigEntityToXml(Collection<EhCacheConfigEntity> configValues) {
        List<String> configXmls = new ArrayList<String>();
        for (EhCacheConfigEntity config : configValues) {
            String xml = this.ehcacheConfigEntityToXml(config, this);
            configXmls.add(xml);
        }
        return configXmls;
    }

    private String ehcacheConfigEntityToXml(EhCacheConfigEntity config, BudoEhcacheAnnotationConfigFactoryBean factory) {
        String xml = "    <cache " //
                + " name=\"" + config.getCacheName() + "\" " //
                + " timeToLiveSeconds=\"" + config.getTimeToLiveSeconds() + "\" "//
                + " timeToIdleSeconds=\"" + config.getTimeToIdleSeconds() + "\" "//
                + " maxElementsInMemory=\"" + config.getMaxElementsInMemory() + "\" ";//

        if (null != config.getMaxElementsOnDisk()) {
            xml += " maxElementsOnDisk=\"" + config.getMaxElementsOnDisk() + "\" ";
            xml += " overflowToDisk=\"true\" ";
        }

        Boolean configStatistics = config.getStatistics();
        Boolean factoryStatistics = factory.getStatistics();
        if ((null != configStatistics && configStatistics) || (null != factoryStatistics && factoryStatistics)) {
            xml += " statistics=\"true\" ";
        }
        xml += " >";

        Class<?> configListener = config.getCacheEventListenerFactory();
        Class<?> factoryListener = factory.getCacheEventListenerFactory();
        if (null != configListener && !void.class.equals(configListener)) {
            xml += "\n        <cacheEventListenerFactory class=\"" + configListener.getName() + "\" /> ";
        } else if (null != factoryListener && !void.class.equals(factoryListener)) {
            xml += "\n        <cacheEventListenerFactory class=\"" + factoryListener.getName() + "\" /> ";
        }

        xml += "\n    </cache>";
        return xml;
    }

    private String configXmlHead() {
        if (null != this.getConfigLocation()) { // 如果有文件
            String confXml = new String(IoUtil.toByteArray(ResourceUtil.getInputStream(this.getConfigLocation())));
            return confXml.trim().replace("</ehcache>", "\n\n\n"); // 去掉尾巴为了追加
        }

        String tmpDir = System.getProperty("java.io.tmpdir");
        String diskStorePath = tmpDir + File.separator + "budo-ehcache" + File.separator + ProcessUtil.getCurrentProcessId();
        log.warn("#177 ehcache.diskStore.path=" + diskStorePath);

        return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" // 没有文件，默认的头
                + "<ehcache>\n" //
                + "<diskStore path=\"" + diskStorePath + "\"/>\n";
    }

    private List<Method> getAllMethods(String beanClassName) {
        try {
            Class type = Class.forName(beanClassName);
            return ReflectUtil.getAllMethods(type);
        } catch (Throwable e) {
            log.warn("#166 getAllMethods of type " + beanClassName + " error, e=" + e);
            return Collections.EMPTY_LIST;
        }
    }

    public Class<?> getObjectType() {
        return Resource.class;
    }

    public boolean isSingleton() {
        return true;
    }
}
